<!DOCTYPE html>
<html>

<head>
  <title>cannon.js - constraints demo</title>
  <meta charset="utf-8">
  <link rel="stylesheet" href="css/style.css" type="text/css" />
  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
</head>

<body>
  <script src="../../feng3d/out/feng3d.js"></script>
  <script src="../out/cannon.js"></script>
  <script src="../build/cannon.demo.js"></script>
  <script src="../libs/dat.gui.js"></script>
  <script src="../libs/Three.js"></script>
  <script src="../libs/TrackballControls.js"></script>
  <script src="../libs/Detector.js"></script>
  <script src="../libs/Stats.js"></script>
  <script src="../libs/smoothie.js"></script>
  <script>

    /**
     * In this demo, we demonstrate some constraint types. Constraints
     * removes degrees of freedom from bodies and forces them to move in
     * a way defined by the constraint.
     */

    var demo = new CANNON.Demo();

    demo.addScene("lock", function ()
    {
      var world = setupWorld(demo);
      world.solver.iterations = 20;
      world.gravity.set(0, 0, -10);
      var size = 0.5;
      var boxShape = new CANNON.Box(new CANNON.Vector3(size, size, size));
      var mass = 1;
      var space = 0.1 * size;

      var N = 10, last;
      for (var i = 0; i < N; i++)
      {
        // Create a box
        var boxbody = new CANNON.Body({
          mass: mass,
          shape: boxShape,
          position: new CANNON.Vector3((N - i - N / 2) * (size * 2 + 2 * space), 0, size * 6 + space)
        });
        world.addBody(boxbody);
        demo.addVisual(boxbody);

        if (last)
        {
          // Connect the current body to the last one
          var c = new CANNON.LockConstraint(boxbody, last);
          world.addConstraint(c);
        }

        // To keep track of which body was added last
        last = boxbody;
      }

      // Create stands
      var bodyA = new CANNON.Body({
        mass: 0,
        shape: boxShape,
        position: new CANNON.Vector3((-N / 2 + 1) * (size * 2 + 2 * space), 0, size * 3)
      });
      world.addBody(bodyA);
      demo.addVisual(bodyA);

      var bodyB = new CANNON.Body({
        mass: 0,
        shape: boxShape,
        position: new CANNON.Vector3((N / 2) * (size * 2 + 2 * space), 0, size * 3)
      });
      world.addBody(bodyB);
      demo.addVisual(bodyB);
    });

    // We link together boxes in a chain
    demo.addScene("links", function ()
    {
      var world = setupWorld(demo);
      world.gravity.set(0, -1, -20);
      var size = 1;
      var boxShape = new CANNON.Box(new CANNON.Vector3(size, size * 0.1, size));
      var mass = 0;
      var space = 0.1 * size;

      var N = 10, last;
      for (var i = 0; i < N; i++)
      {
        // Create a box
        var boxbody = new CANNON.Body({ mass: mass });
        boxbody.addShape(boxShape);
        boxbody.position.set(0, 0, (N - i) * (size * 2 + 2 * space) + size * 2 + space);
        boxbody.linearDamping = 0.01; // Damping makes the movement slow down with time
        boxbody.angularDamping = 0.01;
        world.addBody(boxbody);
        demo.addVisual(boxbody);

        if (i != 0)
        {
          // Connect the current body to the last one
          // We connect two corner points to each other.
          var c1 = new CANNON.PointToPointConstraint(boxbody, new CANNON.Vector3(-size, 0, size + space), last, new CANNON.Vector3(-size, 0, -size - space));
          var c2 = new CANNON.PointToPointConstraint(boxbody, new CANNON.Vector3(size, 0, size + space), last, new CANNON.Vector3(size, 0, -size - space));
          world.addConstraint(c1);
          world.addConstraint(c2);
        } else
        {
          // First body is now static. The rest should be dynamic.
          mass = 0.3;
        }

        // To keep track of which body was added last
        last = boxbody;
      }
    });

    // Particle cloth on sphere
    demo.addScene("cloth on sphere", function ()
    {
      var world = setupWorld(demo);
      world.solver.iterations = 10;

      // To construct the cloth we need Nrows*Ncols particles.
      var dist = 0.2;
      var mass = 0.5;
      var Nrows = 15, Ncols = 15;
      var bodies = {}; // bodies["i j"] => particle
      for (var i = 0; i < Ncols; i++)
      {
        for (var j = 0; j < Nrows; j++)
        {
          // Create a new body
          var body = new CANNON.Body({ mass: mass });
          body.addShape(new CANNON.Particle());
          body.position.set((i - Ncols * 0.5) * dist, (j - Nrows * 0.5) * dist, 5);
          bodies[i + " " + j] = body;
          world.addBody(body);
          demo.addVisual(body);
        }
      }
      // To connect two particles, we use a distance constraint. This forces the particles to be at a constant distance from each other.
      function connect(i1, j1, i2, j2)
      {
        world.addConstraint(new CANNON.DistanceConstraint(bodies[i1 + " " + j1], bodies[i2 + " " + j2], dist));
      }
      for (var i = 0; i < Ncols; i++)
      {
        for (var j = 0; j < Nrows; j++)
        {
          // Connect particle at position (i,j) to (i+1,j) and to (i,j+1).
          if (i < Ncols - 1) connect(i, j, i + 1, j);
          if (j < Nrows - 1) connect(i, j, i, j + 1);
        }
      }

      // Add the static sphere we throw the cloth on top of
      var sphere = new CANNON.Sphere(1.5);
      var body = new CANNON.Body({ mass: 0 });
      body.addShape(sphere);
      body.position.set(0, 0, 3.5);
      world.addBody(body);
      demo.addVisual(body);
    });

    // A pendulum made out of two spheres using a PointToPointConstraint
    demo.addScene("Sphere pendulum", function ()
    {
      var world = setupWorld(demo);
      var size = 1;
      var sphereShape = new CANNON.Sphere(size);
      var mass = 1;

      var spherebody = new CANNON.Body({ mass: mass });
      spherebody.addShape(sphereShape);
      spherebody.position.set(0, 0, size * 3);
      spherebody.velocity.set(5, 0, 0);
      spherebody.linearDamping = 0;
      spherebody.angularDamping = 0;
      world.addBody(spherebody);
      demo.addVisual(spherebody);

      var spherebody2 = new CANNON.Body({ mass: 0 });
      spherebody2.addShape(sphereShape);
      spherebody2.position.set(0, 0, size * 7);
      world.addBody(spherebody2);
      demo.addVisual(spherebody2);

      // Connect this body to the last one
      var c = new CANNON.PointToPointConstraint(spherebody, new CANNON.Vector3(0, 0, size * 2), spherebody2, new CANNON.Vector3(0, 0, -size * 2));
      world.addConstraint(c);
    });

    // Sphere chain
    demo.addScene("Sphere chain", function ()
    {
      var size = 0.5;
      var dist = size * 2 + 0.12;
      var world = setupWorld(demo);
      //world.solver.setSpookParams(1e20,3);
      var sphereShape = new CANNON.Sphere(size);
      var mass = 1;
      var lastBody = null;
      var N = 20;
      world.solver.iterations = N; // To be able to propagate force throw the chain of N spheres, we need at least N solver iterations.
      for (var i = 0; i < N; i++)
      {
        // Create a new body
        var spherebody = new CANNON.Body({ mass: i === 0 ? 0 : mass });
        spherebody.addShape(sphereShape);
        spherebody.position.set(0, 0, (N - i) * dist);
        spherebody.velocity.x = i;
        world.addBody(spherebody);
        demo.addVisual(spherebody);

        // Connect this body to the last one added
        var c;
        if (lastBody !== null)
        {
          world.addConstraint(c = new CANNON.DistanceConstraint(spherebody, lastBody, dist));
        }

        // Keep track of the lastly added body
        lastBody = spherebody;
      }
    });



    // Particle cloth. Same as the previous cloth but here we make the first row of particles static, nailing the cloth it in space
    demo.addScene("Particle cloth", function ()
    {
      var world = setupWorld(demo);
      //world.solver.setSpookParams(1e20,3);
      world.solver.iterations = 18;
      var dist = 0.2;
      var mass = 0.5;
      var Nrows = 15, Ncols = 15;
      var bodies = {}; // bodies["i j"] => particle
      for (var i = 0; i < Ncols; i++)
      {
        for (var j = 0; j < Nrows; j++)
        {
          // Create a new body
          var body = new CANNON.Body({ mass: j == Nrows - 1 ? 0 : mass });
          body.addShape(new CANNON.Particle());
          body.position.set(i * dist, 0, j * dist + 5);
          body.velocity.set(0, 3 * (Math.sin(i * 0.1) + Math.sin(j * 0.1)), 0);
          bodies[i + " " + j] = body;
          world.addBody(body);
          demo.addVisual(body);
        }
      }
      function connect(i1, j1, i2, j2)
      {
        world.addConstraint(new CANNON.DistanceConstraint(bodies[i1 + " " + j1], bodies[i2 + " " + j2], dist));
      }
      for (var i = 0; i < Ncols; i++)
      {
        for (var j = 0; j < Nrows; j++)
        {
          if (i < Ncols - 1) connect(i, j, i + 1, j);
          if (j < Nrows - 1) connect(i, j, i, j + 1);
        }
      }
    });

    // Particle 3d object
    // Distance constraints can be used to construct even cooler things, like this 3d block.
    demo.addScene("3D cloth structure", function ()
    {
      var world = setupWorld(demo);
      world.solver.iterations = 10;
      var dist = 1;
      var mass = 1;
      var Nx = 6, Ny = 3, Nz = 3;
      var bodies = {}; // bodies["i j k"] => particle
      for (var i = 0; i < Nx; i++)
      {
        for (var j = 0; j < Ny; j++)
        {
          for (var k = 0; k < Nz; k++)
          {
            // Create a new body
            var body = new CANNON.Body({ mass: mass });
            body.addShape(new CANNON.Particle());
            body.position.set(i * dist, j * dist, k * dist + Nz * dist * 0.3 + 1);
            body.velocity.set(0, 30 * (Math.sin(i * 0.1) + Math.sin(j * 0.1)), 0);
            bodies[i + " " + j + " " + k] = body;
            world.addBody(body);
            demo.addVisual(body);
          }
        }
      }
      function connect(i1, j1, k1, i2, j2, k2, len)
      {
        world.addConstraint(new CANNON.DistanceConstraint(bodies[i1 + " " + j1 + " " + k1], bodies[i2 + " " + j2 + " " + k2], len));
      }
      for (var i = 0; i < Nx; i++)
      {
        for (var j = 0; j < Ny; j++)
        {
          for (var k = 0; k < Nz; k++)
          {
            // normal directions
            if (i < Nx - 1) connect(i, j, k, i + 1, j, k, dist);
            if (j < Ny - 1) connect(i, j, k, i, j + 1, k, dist);
            if (k < Nz - 1) connect(i, j, k, i, j, k + 1, dist);

            // Diagonals
            if (i < Nx - 1 && j < Ny - 1 && k < Nz - 1)
            {
              // 3d diagonals
              connect(i, j, k, i + 1, j + 1, k + 1, Math.sqrt(3) * dist);
              connect(i + 1, j, k, i, j + 1, k + 1, Math.sqrt(3) * dist);
              connect(i, j + 1, k, i + 1, j, k + 1, Math.sqrt(3) * dist);
              connect(i, j, k + 1, i + 1, j + 1, k, Math.sqrt(3) * dist);

            }

            // 2d diagonals
            if (i < Nx - 1 && j < Ny - 1)
            {
              connect(i + 1, j, k, i, j + 1, k, Math.sqrt(2) * dist);
              connect(i, j + 1, k, i + 1, j, k, Math.sqrt(2) * dist);
            }
            if (i < Nx - 1 && k < Nz - 1)
            {
              connect(i + 1, j, k, i, j, k + 1, Math.sqrt(2) * dist);
              connect(i, j, k + 1, i + 1, j, k, Math.sqrt(2) * dist);
            }
            if (j < Ny - 1 && k < Nz - 1)
            {
              connect(i, j + 1, k, i, j, k + 1, Math.sqrt(2) * dist);
              connect(i, j, k + 1, i, j + 1, k, Math.sqrt(2) * dist);
            }
          }
        }
      }
    });


    function setupWorld(demo)
    {
      // Create world
      var world = demo.getWorld();
      world.gravity.set(0, 0, -40);
      world.broadphase = new CANNON.NaiveBroadphase();
      world.solver.iterations = 10;

      // ground plane
      var groundShape = new CANNON.Plane();
      var groundBody = new CANNON.Body({ mass: 0 });
      groundBody.addShape(groundShape);
      groundBody.position.set(0, 0, 1);
      world.addBody(groundBody);
      demo.addVisual(groundBody);

      world.quatNormalizeFast = false;
      world.quatNormalizeSkip = 0;

      return world;
    };

    demo.start();

  </script>
</body>

</html>